try:
    from cStringIO import StringIO
except:
    import StringIO
from copy import deepcopy
import re
import os
import os.path
import lxml.html as parser
from lxml import etree as xml_parser
import cotton.errors as errors
from cotton.uri import URI

'''
    The cotton.tal module lets you take a Cotton TAL template, pour context into
    it, and print it out. It's very simple! The following (lengthy) example will
    walk you through the details.

    Below is a fairly simple TAL template:

    TEMPLATE.TAL
        <html>
            <head>
                <title tal:content='var:title'>Page Title</title>
            </head>
            <body>
                <p>My name is
                <span tal:content='var:my_info[name]'>NAME</span>.
                I was born in the year of the
                <span tal:content='var:my_info[year]'>YEAR</span>
                <span tal:content='var:my_info[age]'>NUMBER OF YEARS</span>
                years ago.</p>

                <p>I like:</p>

                <ul tal:for='item in var:some_list'>
                    <li tal:content='var:item'>ITEM</li>
                </ul>
            </body>
        </html>

    And here is the python code to add and expand it with context:

    SOMEPAGE.PY
        import cotton.tal as tal
        page = tal.page("C:\\PathToTemplate\\TEMPLATE.TAL")
        my_details = { # NOTE: context dictionary keys must not have spaces!
            'title'     : 'My Home On the Web',
            'some_list' : ['Apples', 'Oranges', 'Bears'],
            'my_info'   : {
                'name'  : 'Monty',
                'age'   : 36,
                'year'  : 'Rat',
                },
            }
        page.add(my_details)
        generated_text = page.expand()

    generated_text will have the following HTML:

    GENERATED HTML PAGE
        <html>
            <head>
                <title>My Home On the Web</title>
            </head>
            <body>
                <p>My name is <span>Monty</span>. I was born in the year of the
                <span>Rat</span> <span>36</span> years ago.</p>

                <p>I like:</p>

                <ul>
                    <li>Apples</li>
                    <li>Oranges</li>
                    <li>Bears</li>
                </ul>
            </body>
        </html>

    But what if you want to use macros? Here's a simple macro example:

    MACRO.TAL
        <html>
            <head><title>Macro</title></head>
            <body>
                <!-- here's the actual macro: -->
                <div class='sidebar' tal:export='sidebar'>
                    <a href='/info'>More Info</a>
                    <a href='/faq'>FAQ Yourself</a>
                    <a href='/doom'>Inevitable Doom</a>
                    <hr/>
                    <p class='small'>
                        (you know you want to click it)
                    </p>
                    <hr/>
                </div>
            </body>
        </html>

    Here's the revised template, which will include the sidebar:

    TEMPLATE.TAL
        <html>
            <head>
                <title tal:content='var:title'>Page Title</title>
            </head>
            <body>
                <div tal:import='sidebar from MACRO.TAL'>SIDEBAR</div>
                <p>My name is
                <span tal:content='var:my_info[name]'>NAME</span>.
                I was born in the year of the
                <span tal:content='var:my_info[year]'>YEAR</span>
                <span tal:content='var:my_info[age]'>NUMBER OF YEARS</span>
                years ago.</p>

                <p>I like:</p>

                <ul tal:for='item in var:some_list'>
                    <li tal:content='var:item'>ITEM</li>
                </ul>
            </body>
        </html>

    The Python code does not change!

    The generated_text will contain:

    GENERATED HTML PAGE
        <html>
            <head>
                <title>My Home On the Web</title>
            </head>
            <body>
                <div class='sidebar'>
                    <a href='/info'>More Info</a>
                    <a href='/faq'>FAQ Yourself</a>
                    <a href='/doom'>Inevitable Doom</a>
                    <hr/>
                    <p class='small'>
                        (you know you want to click it)
                    </p>
                    <hr/>
                </div>
                <p>My name is <span>Monty</span>. I was born in the year of the
                <span>Rat</span> <span>36</span> years ago.</p>

                <p>I like:</p>

                <ul>
                    <li>Apples</li>
                    <li>Oranges</li>
                    <li>Bears</li>
                </ul>
            </body>
        </html>

    NOTES

    0.  The cotton.tal module requires lxml, which requires libxml2. You will
        need both, or it won't work.

    1.  Sloppy HTML/XHTML may have sloppy side effects.
    '''


def _libxml2_drops_namespaces():
    string_1 = """<html xmlns:tal="http://something.com/tal_in_html"><body><p tal:test="success">Success</p></body></html>"""
    document_1 = parser.fromstring(string_1)

    string_2 = """<html xmlns:tal="http://something.com/tal_in_html"><body><p tal_test="success">Success</p></body></html>"""
    document_2 = parser.fromstring(string_2)

    p_attrib_1 = []
    p_attrib_2 = []

    try:
        p_attrib_1 = document_1[0][0].attrib.keys()
    except:
        try:
            p_attrib_2 = document_2[0][0].attrib.keys()
        except:
            raise errors.Libxml2Error(string_1, document_1, "Unable to parse even a simple document. Is Libxml2 installed?")

    if("test" in p_attrib_1 or "tal_test" in p_attrib_2):
        return True
    elif("tal:test" in p_attrib_1):
        return False
    else:
        raise errors.Libxml2Error(string_1, document_1, "We appear to be parsing the document, but losing attributes. No idea why!")

IRRITANT = _libxml2_drops_namespaces()

XML_STARTER = re.compile("""^<\? ?xml.+\n""")
TAL_VALID_ATTRIBUTE = re.compile("""^tal[:_](?P<name>content|define|export|fill|for|if|import|slot)$""")
TAL_ANY_ATTRIBUTE = re.compile("""^tal[:_](.+)$""")
TAL_DEFINE = re.compile(""" *(?P<variable>[A-Za-z_][A-Za-z0-9_]*) as (?P<expression>.+)""")

class page(object):
    '''
        my_template = document(document = "/home/www/htdocs/index.html")
        my_template = document(
            document = "<html>SOME TEMPLATE</html>",
            base_uri = "/home/www/htdocs"
            )
        my_template = document(
            document = "index.html",
            base_uri = "/home/www/htdocs"
        )
        my_template = document(
            document = "index.html",
            base_uri = "/home/www/htdocs",
            context = {'fruits' : ['Apples', 'Pears', 'Bananas',],}
        )
        '''

    def __init__(self, document = "", base_uri = '/', context = {}):
        self.context = {}
        if(context):
            self.add(context)

        self.uri = URI(document, base_uri)
        self.text = ""
        if(self.uri.path()):
            self.text = self.uri.text()
            if(self.text is False):
                raise errors.CottonError(str(self.uri) + " is not a file. Unable to get document text.")
        else:
            self.uri  = URI(base_uri)
            if(self.uri.path() is False):
                raise errors.CottonError(str(base_uri) + " is not a valid URI.")
            self.text = str(document)

    def add(self, context = {}):
        '''
            my_template.add(site_context)
            my_template.add(some_more_context) # forgot to add these elements

            The context variable must be a dictionary. Each dictionary key
            should not have any spaces in it. The value can be anything
            that TAL can use: list, dictionary, string, number, etc.

            You can add context all at once, or multiple times in an additive
            fashion.

            The 'cotton' dictionary key is reserved.

            There are also four special keys, which are stored in cotton, in
            addition to elsewhere:

            'environ'   : this should be a CGI environ dictionary
            'cookies'   : browser cookies (often in 'environ' as well)
            'arguments' : POST or GET data (often in 'environ' as well)
            'here'      : the URI for the script (often in 'environ' as well)
            '''
        for key in context.keys():
            if(key == 'cotton'):
                raise errors.CottonError("The 'cotton' context key is reserved for internal use.")
            elif(key in ['environ', 'cookies', 'arguments', 'here']):
                self.context[key] = context[key]
                self.context['cotton'][key] = context[key]
            else:
                self.context[key] = context[key]

    def template(self):
        """Returns the text string of the template you gave it."""
        return self.text

    def context(self):
        """Returns the starting context object."""
        return self.context

    def expand(self, method="xml", encoding="UTF-8"):
        dom = None
        try:
            dom = xml_parser.parse(StringIO(self.text))
        except lxml.etree.XMLSyntaxError:
            dom = parser.parse(StringIO( XML_STARTER.sub("", self.text) ))

        if(dom is None):
            raise errors.CottonError("Unable to turn text into a DOM object:\n-----\n" + self.text)

        root_element = dom.getroot()

        (root_element, bool_deletion) = _process(root_element, self.context)
        if(bool_deletion):
            root_element = _clear_contents(root_element)

        return parser.tostring(dom, method=method, encoding=encoding)

class _element(object):
    def __init__(self, lxml_element, context = {}):
        self.element = lxml_element
        self.context = context

    def process(self):
        self.tal = {}
        for key in self.attrib.keys():
            match = TAL_VALID_ATTRIBUTE.search(key)
            if(match is not None):
                name = match.group("name")
                self.tal[name] = self.attrib[key]

        ### tal:define
        if("define" in self.tal.keys()):
            definitions = self.tal["define"].split(";")

            for definition in definitions:
                match = TAL_DEFINE.search(definition)
                if(match is not None):
                    variable   = match.group("variable")
                    expression = match.group("expression")
                else: # invalid value
                    continue

                context[variable] = _expression(value, context)


        ### tal:if
        if( _delete(element, context) ): # should we delete this element?
            return (element, True) # skip the rest of the processing if so

        ### tal:import
        element = _import(element, context)

        ### tal:content
        element = _content(element, context)

        ### tal:set
        element = _set(element, context)

        ### tal:for
        (loop_key, loop) = _for(element, context)

        if(loop): # we have a loop; process children differently
            first_clone = deepcopy(element)
            element = _clear_contents(element)
            for (index, loop_value) in enumerate(loop):
                clone_element = deepcopy(first_clone)
                clone_context = context
                clone_context[loop_key] = loop_value
                clone_context['cotton']['index'] = index + 1
                clone_context['cotton']['odd']   = (index + 1) % 2
                if(index == 0):
                    clone_context['cotton']['first'] = 1
                else:
                    clone_context['cotton']['first'] = 0
                if(index + 1 == len(loop)):
                    clone_context['cotton']['last'] = 1
                else:
                    clone_context['cotton']['last'] = 0

                for child in clone_element:
                    (child, bool_deletion) = _process(child, clone_context)
                    if(bool_deletion):
                        if(child.tail):
                            if(child == clone_element[0]):
                                clone_element.text = (clone_element.text or '') + child.tail
                            else:
                                child.getprevious().tail = (child.getprevious().tail or '') + child.tail
                        clone_element.remove(child)

                element.append(clone_element)

            for i in range(len(element)-1, -1, -1):
                _drop_tag(element[i])

        else:     # we do not have a loop; process children normally
            for child in element:
                (child, bool_deletion) = _process(child, context)
                if(bool_deletion):
                    if(child.tail):
                        if(child == element[0]):
                            element.text = (element.text or '') + child.tail
                        else:
                            child.getprevious().tail = (child.getprevious().tail or '') + child.tail
                    element.remove(child)

        ### cleanup
        element = _clean_tal_markup(element)

        return (element, False)

    def tal_attributes(self):
        self.tal = {}
        for key in self.attrib.keys():
            match = TAL_VALID_ATTRIBUTE.search(key)
            if(match):
                name = match.group("name")
                self.tal[name] = self.attrib[key]

EXPRESSION_PREFIX   = re.compile("""^ *(?P<prefix>var|fun|not): *(?P<expression>.+)$""")
VAR                 = re.compile("""^(?P<variable>[A-Za-z_][A-Za-z0-9_]*)(?P<extensions>(\[`?[A-Za-z0-9_]+\])*)$""")
VAR_EXTEND          = re.compile("""\[(`?)([A-Za-z0-9_]+)\]""")
FUN                 = re.compile("""^(?P<module>[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)*)\.(?P<function>[A-Za-z_][A-Za-z]*)\((?P<arguments>[A-Za-z0-9_]+(, *[A-Za-z0-9_]+)*)*\)$""")
FUNCTION            = re.compile("""(?P<module>[A-Za-z_][A-Za-z0-9_\.]*)\.(?P<function>[A-Za-z_][A-Za-z0-9_]*)\((?P<args>[A-Za-z0-9_, ]*)\)""")


def _expression(expression_string, context = {}):
    match = EXPRESSION_PREFIX.match(expression_string)
    if(match is None):
        raise errors.CottonExpressionError(str(expression_string) + " needs a 'var:', 'fun:', or 'not:' prefix.")

    prefix     = match.groups("prefix")
    expression = match.groups("expression")

    if(prefix == "not"):
        value = _expression(expression, context)
        if(value):
            return False
        else:
            return True

    if(prefix == "var"):
        return _var2(expression, context)

    if(prefix == "fun"):
        return _fun2(expression, context)

def _var2(expression, context = {}):
    match = VAR.match(expression)
    if(match is None):
        return None

    var_name   = match['variable']
    if(var_name not in context.keys()):
        message = str(var_name) + " is not available in the current context."
        message = message + "\nThe context currently includes: " + ", ".join(context.keys())
        raise errors.CottonError(message)

    extensions = match['extensions']

    if(extensions):
        extensions = VAR_EXTEND.findall(extensions)

        for (i, extension) in enumerate(extensions):
            (reference, key) = extension
            if(reference):
                extensions[i] = "'" + str(context[key]) + "'"
            elif(str(key).isdigit()):
                pass
            else:
                extensions[i] = "'" + key + "'"

        extensions = "[" + "][".join(extensions) + "]"
    else:
        extensions = ""

    value = ""
    try:
        exec("value = context['" + str(var_name) + "']" + str(extensions))
    except KeyError as err:
        message = "The key(s) " + str(err.args) + " could not be found in " + str(expression)
        raise errors.CottonError(message)

    return value

def _fun2(expression, context = {}):
    result = EXPRESSION_FUNCTION.search(expression)
    if(result is None):
        return False

    result = result.groupdict()

    module_name   = result['module']
    function_name = result['function']
    arguments     = result['args']

    if(module_name and function_name):
        try:
            import_string = "from " + str(module_name) + " import " + str(function_name)
            exec(import_string)
        except ImportError:
            return False

    function_call = str(function_name) + "()"
    arguments = arguments.replace(" ", "")
    if(arguments and arguments != ''):
        arguments = arguments.split(",")
        arguments = [arg.replace(" ", "") for arg in arguments]

        for i in range(len(arguments)):
            arguments[i] = "context['" + arguments[i] + "']"

        arguments = ",".join(arguments)

        function_call = str(function_name) + "(" + str(arguments) + ")"

    value = ''
    try:
        value = eval(function_call)
    except:
        value = False

    exec("del " + str(function_name))

    return value

### INTERFACE FOR OUTSIDE WORLD
class document(object):
    '''
        my_template = page(file_name_or_plain_text)

        Creates a page object which you can pour context into.

        See the main documentation for the full process for using this
        module.
        '''
    def __init__(self, document, base_uri = '/'):
        uri = _get_uri(document, base_uri)
        self.context = {
            'cotton' : {
                'base_uri'  : _uri(base_uri),
                'arguments' : None,
                'cookies'   : None,
                'environ'   : None,
                },
            }

        if(uri):
            self.context['cotton']['document_uri'] = _uri(uri)
            self.context['cotton']['document']     = _get_text(uri)
        else:
            self.context['cotton']['document_uri'] = _uri(base_uri)
            self.context['cotton']['document']     = _get_text(document)

    def add(self, context = {}):
        '''
            my_template.add(site_context)
            my_template.add(some_more_context) # forgot to add these elements

            The context variable must be a dictionary. Each dictionary key
            should not have any spaces in it. The value can be anything
            that TAL can use: list, dictionary, string, number, etc.

            You can add context all at once, or multiple times in an additive
            fashion.
            '''
        for key in context.keys():
            if(key == 'cotton'):
                pass # this key is reserved for Cotton's use!
            elif(key in ['environ', 'cookies', 'arguments', 'here']):
                self.context['cotton'][key] = context[key]
                if(key == 'arguments'): # This will be deprecated once I fix other code
                    self.context[key] = context[key]
            else:
                self.context[key] = context[key]

    def expand(self, method='xml', encoding='UTF-8'):
        '''
            print my_template.expand()
            # or
            generated_text = my_template.expand()

            Expands the template with any context added so far.
            '''

        dom = _dom(self.context['cotton']['document'])
        root_element = dom.getroot()
        (root_element, bool_deletion) = _process(root_element, self.context)
        if(bool_deletion):
            root_element = _clear_contents(root_element)

        return parser.tostring(dom, method=method, encoding=encoding)
#
### HIGH LEVEL DOCUMENT MANIPULATION
def _process( element, context = {} ):
    """
        Recursively steps through an LXML document object, handling all
        Cotton markup for each element. Returns the final, processed
        element (including all children elements).
        """

    ### tal:define
    context = _define(element, context)

    ### tal:if
    if( _delete(element, context) ): # should we delete this element?
        return (element, True) # skip the rest of the processing if so

    ### tal:import
    element = _import(element, context)

    ### tal:content
    element = _content(element, context)

    ### tal:set
    element = _set(element, context)

    ### tal:for
    (loop_key, loop) = _for(element, context)

    if(loop): # we have a loop; process children differently
        first_clone = deepcopy(element)
        element = _clear_contents(element)
        for (index, loop_value) in enumerate(loop):
            clone_element = deepcopy(first_clone)
            clone_context = context
            clone_context[loop_key] = loop_value
            clone_context['cotton']['index'] = index + 1
            clone_context['cotton']['odd']   = (index + 1) % 2
            if(index == 0):
                clone_context['cotton']['first'] = 1
            else:
                clone_context['cotton']['first'] = 0
            if(index + 1 == len(loop)):
                clone_context['cotton']['last'] = 1
            else:
                clone_context['cotton']['last'] = 0

            for child in clone_element:
                (child, bool_deletion) = _process(child, clone_context)
                if(bool_deletion):
                    if(child.tail):
                        if(child == clone_element[0]):
                            clone_element.text = (clone_element.text or '') + child.tail
                        else:
                            child.getprevious().tail = (child.getprevious().tail or '') + child.tail
                    clone_element.remove(child)

            element.append(clone_element)

        for i in range(len(element)-1, -1, -1):
            _drop_tag(element[i])

    else:     # we do not have a loop; process children normally
        for child in element:
            (child, bool_deletion) = _process(child, context)
            if(bool_deletion):
                if(child.tail):
                    if(child == element[0]):
                        element.text = (element.text or '') + child.tail
                    else:
                        child.getprevious().tail = (child.getprevious().tail or '') + child.tail
                element.remove(child)

    ### cleanup
    element = _clean_tal_markup(element)

    return (element, False)

def _dom(text):
    """
        Given a string or file handle, returns an LXML document
        object.
        """
    text = _get_text(text)

    dom = None
    try:
        dom = xml_parser.parse(StringIO(text))
    except:
        text = XML_STARTER.sub("", text)
        dom = parser.parse(StringIO(text))

    return dom

def _partial_element(text):
    text = _get_text(text)

    dom = parser.fromstring(text)

    return dom

IRRITANT_NAMESPACE_MATCH = re.compile(""" tal:([a-z]+=('|"))(?=[^<>]*>)""")
def _get_text(document, base_uri = ''):
    text = ''
    document = str(document)
    base_uri = str(base_uri)

    uri = _get_uri(document, base_uri)
    if(uri):
        uri = _uri(uri)
        file_handle = open(uri, 'r')
        text = "".join(file_handle.readlines())
        file_handle.close()
    else:
        text = document

    if(IRRITANT):
        text = IRRITANT_NAMESPACE_MATCH.sub(" tal_\g<1>", text)

    return text

def _get_uri(tail, head = ''):
    head = _uri(head)
    tail = _uri(tail)
    if(os.path.isfile(head + tail)):
        return head + tail

    if(os.path.isfile(os.path.join(head, tail))):
        return os.path.join(head, tail)

    return False


#
### LOW LEVEL PER-ATTRIBUTE ELEMENT MANIPULATION
TAL_WILDCARD = re.compile("""^tal[:_].+""")
def _clean_tal_markup(element):
    for key in element.attrib.keys():
        result = TAL_WILDCARD.search(key)
        if(result is not None):
            del element.attrib[key]

    return element

def _clear_contents(element):
    for child in element:
        element.remove(child)
    element.text = ''

    return element

def _drop_tag(element):
    child = element
    parent = child.getparent()
    previous = child.getprevious()
    if(child.text and isinstance(child.tag, basestring)):
        if(previous is None):
            parent.text = (parent.text or '') + child.text
        else:
            previous.tail = (previous.tail or '') + child.text
    if(child.tail):
        if(len(child)):
            last = child[-1]
            last.tail = (last.tail or '') + child.tail
        elif(previous is None):
            parent.text = (parent.text or '') + child.tail
        else:
            previous.tail = (previous.tail or '') + child.tail
    index = parent.index(child)
    parent[index:index+1] = child[:]

DEFINE = re.compile(""" *([A-Za-z_][A-Za-z0-9_]*) as (.+)""")
def _define(element, context = {}):
    attribute = _tal(element, 'define')
    if(attribute):
        definitions = attribute.split(";")

        for definition in definitions:
            definition = DEFINE.search(definition)
            if(definition is not None):
                definition = definition.groups()
            else: # invalid value
                continue

            (key, value) = definition
            context[key] = _expression(value, context)

    return context

def _delete(element, context = {}): # tal:if, but named for code clarity
    attribute = _tal(element, 'if')
    if(attribute):
        truth = _expression(attribute, context)
        if(truth is None): # Invalid expression. DO NOT DELETE.
            return False
        elif(truth): # DO NOT DELETE.
            return False
        else: # DELETE
            return True

    return False # No tal:if expression. DO NOT DELETE.

IMPORT = re.compile("""([A-Za-z_][A-Za-z0-9_]*) from (.+)""")
def _import(element, context = {}):
    attribute = _tal(element, 'import')

    if(attribute):
        definition = IMPORT.search(attribute)
        if(definition):
            (macro_name, uri_string) = definition.groups()

            uri_string = _find_path(uri_string, context)

            ### tal:export
            exported_element = _partial_element(uri_string)

            result = _export(exported_element, macro_name)

            if(result[1] == True): # macro is available
                exported_element = result[0]

                ### tal:slot
                #
                exported_element = _slot(exported_element, element, context)

                (exported_element, bool_deletion) = _process(exported_element, context)
                if(bool_deletion):
                    exported_element = _clear_contents(exported_element)

                element = _clear_contents(element)
                element.append(exported_element)

                for key in element.attrib.keys():
                    del element.attrib[key]
                for key in element[0].attrib.keys():
                    element.attrib[key] = element[0].attrib[key]

                _drop_tag(element[0])

                return element

    return element

def _export(element, macro_name):
    attribute = _tal(element, 'export')

    if(attribute):
        if(attribute == macro_name):
            element.tail = None
            return (element, True)
    else:
        for child in element:
            result = _export(child, macro_name)
            if(result[1] == True):
                result[0].tail = None
                return result

    return (element, False)

def _slot(element_with_slots, element_with_fills, context):
    attribute = _tal(element_with_slots, 'slot')

    if(attribute):
        slot_name = attribute

        result = _fill(element_with_fills, slot_name)

        if(result[1] == True): # fill is available
            fill_element = result[0]

            (fill_element, bool_deletion) = _process(fill_element, context)
            if(bool_deletion):
                fill_element = _clear_contents(fill_element)

            element_with_slots = _clear_contents(element_with_slots)
            element_with_slots.append(fill_element)

            for key in element_with_slots.attrib.keys():
                del element_with_slots.attrib[key]
            for key in element_with_slots[0].attrib.keys():
                element_with_slots.attrib[key] = element_with_slots[0].attrib[key]

            _drop_tag(element_with_slots)

        return element_with_slots

    for child in element_with_slots:
        child = _slot(child, element_with_fills, context)

    return element_with_slots

def _fill(element, slot_name):
    attribute = _tal(element, 'fill')

    if(attribute):
        if(attribute == slot_name):
            element.tail = None
            return (element, True)
    else:
        for child in element:
            result = _fill(child, slot_name)
            if(result[1] == True):
                result[0].tail = None
                return result

    return (element, False)

def _content(element, context = {}):
    attribute = _tal(element, 'content')
    if(attribute):
        new_content = _expression(attribute, context)
        if(new_content):
            new_content = _partial_element("<div>" + str(new_content) + "</div>")
            element = _clear_contents(element)
            element.append(new_content)
            _drop_tag(element[0])

    return element

SET = re.compile("""([A-Za-z_][A-Za-z0-9_]*) to (.+)""")
def _set(element, context = {}):
    attribute = _tal(element, 'set')
    if(attribute):
        definitions = attribute.split(";")

        for definition in definitions:
            definition = SET.search(definition).groups()

            if(not(definition)): # invalid value
                continue

            (key, value) = definition
            element.attrib[key] = str(_expression(value, context))

    return element

FOR = re.compile("""([A-Za-z_][A-Za-z0-9_]*) in (.+)""")
def _for(element, context = {}):
    attribute = _tal(element, 'for')
    key = ''
    loop = tuple()

    if(attribute):
        definition = FOR.search(attribute).groups()

        if(not(definition)): # invalid value
            return element

        (key, value) = definition
        loop = tuple(_expression(value, context))

    return (key, loop)
#
### LOW LEVEL SUPPORT FUNCTIONS
def _tal(element, attribute_name):
    """
        Accepts an element and the name of the type of tal attribute
        desired, and returns the value of that attribute, if any.
        """
    attribute_name = _tal_prefix() + str(attribute_name)

    if(attribute_name in element.attrib.keys()):
        return element.attrib[attribute_name]
    else:
        return ''

def _tal_prefix():
    prefix = "tal:"
    if(IRRITANT):
        prefix = "tal_"

    return prefix

EXPRESSION_PREFIX = re.compile("""^(var|fun|not):(.+)$""")
def _expression(expression, context = {}):
    """
        Accepts an expression and the context object, and interprets the
        expression. In the case of 'not:...' expressions, this is
        recursive.
        """

    result = EXPRESSION_PREFIX.search(expression)
    if(result is None):
        return False

    (prefix, expression) = result.groups()

    if(prefix == 'not'):
        if(_expression(expression, context)):
            return False
        else:
            return True

    if(prefix == 'var'):
        return _var(expression, context)

    if(prefix == 'fun'):
        return _fun(expression, context)

    return False

EXPRESSION_VAR_NAME       = re.compile("""^([A-Za-z_][A-Za-z0-9_]*)(?=\[|$)(.*)$""")
EXPRESSION_VAR_EXTEND     = re.compile("""\[(`?[A-Za-z0-9_]+)\]""")
EXPRESSION_VAR_EXTEND_INT = re.compile("""^([0-9]+)$""")
def _var(expression, context = {}):
    result = EXPRESSION_VAR_NAME.search(expression)
    if(result is None):
        return None

    (var_name, extensions) = result.groups()

    if(extensions):
        extensions = EXPRESSION_VAR_EXTEND.findall(extensions)

        for i in range(len(extensions)):
            if(extensions[i][0] == '`'): # use as a string variable
                extensions[i] = "'" + str(context[extensions[i][1:]]) + "'"
            elif(EXPRESSION_VAR_EXTEND_INT.match(extensions[i])):
                extensions[i] = extensions[i]
            else:
                extensions[i] = "'" + str(extensions[i]) + "'"

        extensions = "[" + "][".join(extensions) + "]"

    value = ""
    exec("value = context['" + str(var_name) + "']" + str(extensions))

    return value

EXPRESSION_FUNCTION = re.compile("""(?P<module>[A-Za-z_][A-Za-z0-9_\.]*)\.(?P<function>[A-Za-z_][A-Za-z0-9_]*)\((?P<args>[A-Za-z0-9_, ]*)\)""")
def _fun(expression, context = {}):
    result = EXPRESSION_FUNCTION.search(expression)
    if(result is None):
        return False

    result = result.groupdict()

    module_name   = result['module']
    function_name = result['function']
    arguments     = result['args']

    if(module_name and function_name):
        try:
            import_string = "from " + str(module_name) + " import " + str(function_name)
            exec(import_string)
        except ImportError:
            return False

    function_call = str(function_name) + "()"
    arguments = arguments.replace(" ", "")
    if(arguments and arguments != ''):
        arguments = arguments.split(",")
        arguments = [arg.replace(" ", "") for arg in arguments]

        for i in range(len(arguments)):
            arguments[i] = "context['" + arguments[i] + "']"

        arguments = ",".join(arguments)

        function_call = str(function_name) + "(" + str(arguments) + ")"

    value = ''
    try:
        value = eval(function_call)
    except:
        value = False

    exec("del " + str(function_name))

    return value

def _uri(string):
    chain = _chain(string)
    return _string(chain)

def _chain(string):
    (head, tail) = os.path.split(string)
    if(os.path.join(head) == string):
        return (head,)
    else:
        return _chain(head) + (tail,)

def _string(chain):
    return os.path.join(*chain)

def _find_path(uri_string, context):
    root_uri = _uri(context['cotton']['base_uri'])
    source_uri = _uri(os.path.split(context['cotton']['document_uri'])[0])

    # At this point, we know two directories: the top level web directory
    # (root_uri) and the directory that the originating document is in (source_uri).

    uri_options = [source_uri, root_uri, '',]

    if(os.path.isabs(uri_string)):
        # try the root uri first
        uri_options = [root_uri,] + uri_options

    for option in uri_options:
        uri = _get_uri(uri_string, option)
        if(uri):
            return uri

    return False
